package com.gradecak.alfresco.mvc.util;

import java.io.Serializable;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;

import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.namespace.QName;
import org.springframework.util.StringUtils;

public class Query {
  private final SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.ENGLISH);
  private final StringBuilder buffer = new StringBuilder();

  public Query exactAnd(Map<QName, Serializable> properties) {
    for (Entry<QName, Serializable> entry : properties.entrySet()) {
      Object value = entry.getValue();

      String convert = value == null ? null : convert(value);
      if (StringUtils.hasText(convert)) {
        this.and();
        this.property(entry.getKey()).exact(value);
      }
    }

    return this;
  }

  public Query exactOr(Map<QName, Serializable> properties) {
    for (Entry<QName, Serializable> entry : properties.entrySet()) {
      Object value = entry.getValue();

      String convert = value == null ? null : convert(value);
      if (StringUtils.hasText(convert)) {
        this.or();
        this.property(entry.getKey()).exact(value);
      }
    }

    return this;
  }

  public Query likeAnd(Map<QName, Serializable> properties) {
    for (Entry<QName, Serializable> entry : properties.entrySet()) {
      Object value = entry.getValue();

      String convert = value == null ? null : convert(value);
      if (StringUtils.hasText(convert)) {
        this.and();
        this.property(entry.getKey()).like(convert.toLowerCase());
      }
    }

    return this;
  }

  public Query likeOr(Map<QName, Serializable> properties) {
    for (Entry<QName, Serializable> entry : properties.entrySet()) {
      Object value = entry.getValue();

      String convert = value == null ? null : convert(value);
      if (StringUtils.hasText(convert)) {
        this.or();
        this.property(entry.getKey()).like(convert.toLowerCase());
      }
    }

    return this;
  }

  public Query containsAnd(Map<QName, Serializable> properties) {
    for (Entry<QName, Serializable> entry : properties.entrySet()) {
      Object value = entry.getValue();

      String convert = value == null ? null : convert(value);
      if (StringUtils.hasText(convert)) {
        this.and();
        this.property(entry.getKey()).contains(value);
      }
    }

    return this;
  }

  public Query containsOr(Map<QName, Serializable> properties) {
    return containsOr(properties, true);
  }

  // TODO dmurat: add useLeading(Or/And) to other methods where appropriate
  public Query containsOr(Map<QName, Serializable> properties, boolean useLeadingOr) {
    boolean firstPass = true;
    for (Entry<QName, Serializable> entry : properties.entrySet()) {
      Object value = entry.getValue();

      String convert = value == null ? null : convert(value);
      if (StringUtils.hasText(convert)) {
        if (firstPass && !useLeadingOr) {
          this.property(entry.getKey()).contains(value);
          firstPass = false;
        }
        else {
          this.or();
          this.property(entry.getKey()).contains(value);
        }
      }
    }

    return this;
  }

  public Query append(String expression) {
    buffer.append(expression);
    return this;
  }

  public Query exact(Object pCriteria) {
    String criteria = convert(pCriteria);
    buffer.append("\"");
    buffer.append(criteria);
    buffer.append("\"");

    return this;
  }

  public Query like(Object pCriteria) {
    String criteria = convert(pCriteria);
    buffer.append("\"");
    buffer.append(criteria);
    buffer.append("*\"");

    return this;
  }

  public Query contains(Object pCriteria) {
    String criteria = convert(pCriteria);
    buffer.append("\"*");
    buffer.append(criteria);
    buffer.append("*\"");

    return this;
  }

  public Query property(QName property) {
    buffer.append("@");
    buffer.append(AlfrescoUtil.escapeQName(property));
    buffer.append(":");
    return this;
  }

  public Query isNotNull(QName property) {
    buffer.append("ISNOTNULL");
    buffer.append(":");
    buffer.append("\"");
    buffer.append(AlfrescoUtil.escapeQName(property));
    buffer.append("\"");
    return this;
  }

  public Query isNull(QName property) {
    buffer.append("ISNULL");
    buffer.append(":");
    buffer.append("\"");
    buffer.append(AlfrescoUtil.escapeQName(property));
    buffer.append("\"");
    return this;
  }

  public Query isUnset(QName property) {
    buffer.append("ISUNSET");
    buffer.append(":");
    buffer.append("\"");
    buffer.append(AlfrescoUtil.escapeQName(property));
    buffer.append("\"");
    return this;
  }

  public Query id(String id) { // NOPMD by dmurat on 1/15/12 5:29 PM
    buffer.append("ID:\"");
    buffer.append(AlfrescoUtil.escape(id));
    buffer.append("\"");
    return this;
  }

  public Query type(QName type) {
    buffer.append("TYPE:\"");
    buffer.append(AlfrescoUtil.escapeQName(type));
    buffer.append("\"");
    return this;
  }

  public Query parent(NodeRef parentRef) {
    buffer.append("PARENT:\"");
    buffer.append(parentRef.toString());
    buffer.append("\"");
    return this;
  }

  public Query aspect(QName aspect) {
    buffer.append("ASPECT:\"");
    buffer.append(AlfrescoUtil.escapeQName(aspect));
    buffer.append("\"");
    return this;
  }

  public Query or() { // NOPMD by dmurat on 1/15/12 5:30 PM
    if (buffer.length() != 0) {
      buffer.append(" OR ");
    }
    return this;
  }

  public Query not() {
    buffer.append(" NOT ");
    return this;
  }

  public Query and() {
    if (buffer.length() != 0) {
      buffer.append(" AND ");
    }
    return this;
  }

  public Query range(Object start, Object end) {
    buffer.append("[");
    buffer.append(start != null ? convert(start) : "MIN");
    buffer.append(" TO ");
    buffer.append(end != null ? convert(end) : "MAX");
    buffer.append("]");
    return this;
  }

  private String convert(Object value) {
    if (value instanceof Date) {
      return simpleDateFormat.format(value);
    }

    return value.toString();
  }

  @Override
  public String toString() {
    return buffer.toString();
  }

}
